我们还没有讨论在哪里放置目标描述文件，LLVM中的每个后端在llvm/lib/Target中都有一个子目录。这里创建M88k目录，并将目标描述文件复制到其中。

当然，仅添加TableGen文件是不够的。LLVM使用注册表查找目标实现的实例，并期望某些全局函数注册这些实例。由于已经生成了一些部分，所以已经可以提供一个实现。

关于目标的所有信息，如目标机器、汇编器、反汇编器等的目标三元组和工厂函数，都存储在target类的实例中。每个目标都持有该类的一个静态实例，这个实例在中心注册表中注册:

\begin{enumerate}
\item
实现在目标的TargetInfo子目录下的M88kTargetInfo.cpp文件中。Target类的单个实例保存在getTheM88kTarget()函数中:

\begin{cpp}
using namespace llvm;
Target &llvm::getTheM88kTarget() {
    static Target TheM88kTarget;
    return TheM88kTarget;
}
\end{cpp}

\item
LLVM要求每个目标提供一个LLVMInitialize<Target Name> TargetInfo()函数来注册目标实例。这个函数必须有一个C链接，可以在LLVM的C API中使用:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTargetInfo() {
    RegisterTarget<Triple::m88k, /*HasJIT=*/false> X(
        getTheM88kTarget(), "m88k", "M88k", "M88k");
}
\end{cpp}

\item
还需要在同一目录下创建一个M88kTargetInfo.h头文件，只包含一个声明:

\begin{cpp}
namespace llvm {
class Target;
Target &getTheM88kTarget();
}
\end{cpp}

\item
最后，添加一个CMakeLists.txt文件用于构建:

\begin{cmake}
add_llvm_component_library(LLVMM88kInfo
    M88kTargetInfo.cpp
    LINK_COMPONENTS Support
    ADD_TO_COMPONENT M88k)
\end{cmake}

\end{enumerate}

接下来，用机器代码(MC)级别使用的信息部分填充目标实例:

\begin{enumerate}
\item
实现在MCTargetDesc子目录下的M88kMCTargetDesc.cpp文件中，TableGen将前一节中创建的目标描述转换为C++源码段。这里，我们包括寄存器信息、指令信息和子目标信息的部分:

\begin{cpp}
using namespace llvm;

#define GET_INSTRINFO_MC_DESC
#include "M88kGenInstrInfo.inc"

#define GET_SUBTARGETINFO_MC_DESC
#include "M88kGenSubtargetInfo.inc"

#define GET_REGINFO_MC_DESC
#include "M88kGenRegisterInfo.inc"
\end{cpp}

\item
目标注册中心期望这里的每个类都有一个工厂方法。从指令信息开始，分配一个MCInstrInfo类的实例，并调用InitM88kMCInstrInfo()生成的函数来填充对象:

\begin{cpp}
static MCInstrInfo *createM88kMCInstrInfo() {
    MCInstrInfo *X = new MCInstrInfo();
    InitM88kMCInstrInfo(X);
    return X;
}
\end{cpp}

\item
接下来，分配MCRegisterInfo类的对象，并调用生成的函数来填充。M88k::R1参数值告诉LLVM R1寄存器保存着返回地址:

\begin{cpp}
static MCRegisterInfo *
createM88kMCRegisterInfo(const Triple &TT) {
    MCRegisterInfo *X = new MCRegisterInfo();
    InitM88kMCRegisterInfo(X, M88k::R1);
    return X;
}
\end{cpp}

\item
最后，需要一个用于子目标信息的工厂方法。这个方法接受一个目标三元组、一个CPU名称和一个特征字符串作为参数，并将它们转发给生成函数:

\begin{cpp}
static MCSubtargetInfo *
createM88kMCSubtargetInfo(const Triple &TT,
StringRef CPU, StringRef FS) {
    return createM88kMCSubtargetInfoImpl(TT, CPU,
                                        /*TuneCPU*/ CPU,
                                        FS);
}
\end{cpp}

\item
定义了工厂方法之后，现在可以注册它们了。与目标注册类似，LLVM期望一个名为LLVMInitialize<Target Name> TargetMC()的全局函数:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTargetMC() {
    TargetRegistry::RegisterMCInstrInfo(
        getTheM88kTarget(), createM88kMCInstrInfo);
    TargetRegistry::RegisterMCRegInfo(
        getTheM88kTarget(), createM88kMCRegisterInfo);
    TargetRegistry::RegisterMCSubtargetInfo(
        getTheM88kTarget(), createM88kMCSubtargetInfo);
}
\end{cpp}

\item
M88kMCTargetDesc.h头文件只是使一些生成的代码可用:

\begin{cpp}
#define GET_REGINFO_ENUM
#include "M88kGenRegisterInfo.inc"

#define GET_INSTRINFO_ENUM
#include "M88kGenInstrInfo.inc"

#define GET_SUBTARGETINFO_ENUM
#include "M88kGenSubtargetInfo.inc"
\end{cpp}
\end{enumerate}

实现差不多完成了。为了避免链接器错误，需要提供另一个函数，该函数为TargetMachine类的对象注册一个工厂方法。这个类是代码生成所必需的，将在第12章中实现。这里，只是在M88kTargetMachine.cpp文件中定义了一个空函数:

\begin{cpp}
#include "TargetInfo/M88kTargetInfo.h"
#include "llvm/MC/TargetRegistry.h"

extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTarget() {
    // TODO Register the target machine. See chapter 12.
}
\end{cpp}

这就是我们的第一个实现，但LLVM还不知道添加了新后端。为了集成它，打开llvm/CMakeLists.txt文件，找到定义所有实验目标的部分，将M88k目标添加到列表中:

\begin{cmake}
set(LLVM_ALL_EXPERIMENTAL_TARGETS ARC … M88k …)
\end{cmake}

假设新后端LLVM源代码在目录中，可以通过输入以下命令来配置构建:

\begin{shell}
$ mkdir build
$ cd build
$ cmake -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=M88k \
../llvm-m88k/llvm
…
-- Targeting M88k
…
\end{shell}

构建LLVM之后，可以验证工具已经知道我们的新目标:

\begin{shell}
$ bin/llc –version
LLVM (http://llvm.org/):
    LLVM version 17.0.2
    Registered Targets:
        m88k - M88k
\end{shell}

到达这里还是有一些困难的，现在可以庆祝一下了!

\begin{myTip}{修复可能的编译错误}
LLVM 17.0.2中有一个问题，会导致编译错误。在代码中的一个地方，子目标信息的TableGen发射器使用已删除的值llvm::None，而非std::nullopt，从而在编译M88kMCTargetDesc.cpp时导致错误。修复此问题的最简单方法是从LLVM 18开发分支中挑选修复该问题的提交:git cherry-pick -x a587f429。
\end{myTip}

下一节中，将实现汇编解析器，这将是我们第一个实现的LLVM工具。


















































